﻿using System;
using System.Collections.Generic;
using System.Text;
using Qreed.Windows.Forms.Hook;
using System.Windows.Forms;
using System.Diagnostics;
using System.Drawing;

namespace Qreed.Windows.Forms
{
    /// <summary>
    /// 
    /// </summary>
    /// <remarks>
    /// MessageBoxEx is a modified version of the one created by Mike Scanlon.
    /// http://www.codeproject.com/KB/dialog/MessageBoxChk.aspx
    /// </remarks>
    public class MessageBoxEx
    {
        private IntPtr _windowHandle = IntPtr.Zero;
        private IntPtr _checkBoxHandle = IntPtr.Zero;
        private bool _checkBoxCreated;

        private string _checkboxText;
        /// <summary>
        /// Gets or sets the check box text.
        /// </summary>
        /// <value>The check box text.</value>
        public string CheckBoxText
        {
            get { return _checkboxText; }
            set { _checkboxText = value; }
        }

        private bool _checkBoxChecked;
        /// <summary>
        /// Gets or sets a value indicating whether the check box has been checked.
        /// </summary>
        /// <value><c>true</c> if the check box us checked; otherwise, <c>false</c>.</value>
        public bool CheckBoxChecked
        {
            get { return _checkBoxChecked; }
            set { _checkBoxChecked = value; }
        }

        private string _text;
        /// <summary>
        /// Gets or sets the text.
        /// </summary>
        /// <value>The text.</value>
        public string Text
        {
            get { return _text; }
            set { _text = value; }
        }

        private string _caption;
        /// <summary>
        /// Gets or sets the caption.
        /// </summary>
        /// <value>The caption.</value>
        public string Caption
        {
            get { return _caption; }
            set { _caption = value; }
        }

        private MessageBoxButtons _buttons;
        /// <summary>
        /// Gets or sets the message box buttons.
        /// </summary>
        /// <value>The message box buttons.</value>
        public MessageBoxButtons Buttons
        {
            get { return _buttons; }
            set { _buttons = value; }
        }

        private MessageBoxDefaultButton _defaultButton;
        /// <summary>
        /// Gets or sets the default button.
        /// </summary>
        /// <value>The default button.</value>
        public MessageBoxDefaultButton DefaultButton
        {
            get { return _defaultButton; }
            set { _defaultButton = value; }
        }

        private MessageBoxIcon _icon;
        /// <summary>
        /// Gets or sets the icon.
        /// </summary>
        /// <value>The icon.</value>
        public MessageBoxIcon Icon
        {
            get { return _icon; }
            set { _icon = value; }
        }

        private IWin32Window _owner;
        /// <summary>
        /// Gets or sets the owner.
        /// </summary>
        /// <value>The owner.</value>
        public IWin32Window Owner
        {
            get { return _owner; }
            set { _owner = value; }
        }

        private MessageBoxOptions _options;
        /// <summary>
        /// Gets or sets the options.
        /// </summary>
        /// <value>The options.</value>
        public MessageBoxOptions Options
        {
            get { return _options; }
            set { _options = value; }
        }

        #region Win32 Window Messages
        private const uint WS_VISIBLE = 0x10000000;
        private const uint WS_CHILD = 0x40000000;
        private const uint WS_TABSTOP = 0x00010000;
        private const uint WM_SETFONT = 0x00000030;
        private const uint WM_GETFONT = 0x00000031;
        private const uint BS_AUTOCHECKBOX = 0x00000003;
        private const uint BM_GETCHECK = 0x00F0;
        private const uint BM_SETCHECK = 0x00F1;
        private const uint BST_CHECKED = 0x0001;
        private const uint BST_UNCHECKED = 0x0000;
        #endregion



        /// <summary>
        /// Display a messagebox with a checkbox.
        /// </summary>
        /// <param name="owner">The owner window.</param>
        /// <param name="text">The text to display.</param>
        /// <param name="caption">The caption to use.</param>
        /// <param name="checkBoxText">The checkbox text.</param>
        /// <param name="isChecked">Indicates if the checkbox is checked.</param>
        /// <param name="buttons">The buttons to display.</param>
        /// <param name="icon">The icon to use.</param>
        /// <returns>
        /// The <see cref="DialogResult"/> response.
        /// </returns>
        public static DialogResult Show(IWin32Window owner, string text, string caption,
                                        string checkBoxText, ref bool isChecked, 
                                        MessageBoxButtons buttons,
                                        MessageBoxIcon icon)
        {
            return Show(owner, text, caption, checkBoxText, ref isChecked, buttons, icon,
                        MessageBoxDefaultButton.Button1, 
                        0);
        }

        /// <summary>
        /// Display a messagebox with a checkbox.
        /// </summary>
        /// <param name="text">The text to display.</param>
        /// <param name="caption">The caption to use.</param>
        /// <param name="checkBoxText">The checkbox text.</param>
        /// <param name="isChecked">Indicates if the checkbox is checked.</param>
        /// <param name="buttons">The buttons to display.</param>
        /// <param name="icon">The icon to use.</param>
        /// <returns>The <see cref="DialogResult"/> response.</returns>
        public static DialogResult Show(string text, string caption,
                                        string checkBoxText, ref bool isChecked,
                                        MessageBoxButtons buttons,
                                        MessageBoxIcon icon)
        {
            return Show(null, text, caption, checkBoxText, ref isChecked, buttons, icon,
                        MessageBoxDefaultButton.Button1,
                        0);
        }

        /// <summary>
        /// Display a messagebox with a checkbox.
        /// </summary>
        /// <param name="text">The text to display.</param>
        /// <param name="caption">The caption to use.</param>
        /// <param name="checkBoxText">The checkbox text.</param>
        /// <param name="isChecked">Indicates if the checkbox is checked.</param>
        /// <returns>The <see cref="DialogResult"/> response.</returns>
        public static DialogResult Show(string text, string caption,
                                        string checkBoxText, ref bool isChecked)
        {
            return Show(null, text, caption, checkBoxText, ref isChecked, MessageBoxButtons.OK, 
                        MessageBoxIcon.None,
                        MessageBoxDefaultButton.Button1,
                        0);
        }

        /// <summary>
        /// Display a messagebox with a checkbox.
        /// </summary>
        /// <param name="owner">The owner window.</param>
        /// <param name="text">The text to display.</param>
        /// <param name="caption">The caption to use.</param>
        /// <param name="checkBoxText">The checkbox text.</param>
        /// <param name="isChecked">Indicates if the checkbox is checked.</param>
        /// <param name="buttons">The buttons to display.</param>
        /// <param name="icon">The icon to use.</param>
        /// <param name="defaultButton">The default button.</param>
        /// <param name="options">The options.</param>
        /// <returns>
        /// The <see cref="DialogResult"/> response.
        /// </returns>
        public static DialogResult Show(IWin32Window owner, string text, string caption,
                                        string checkBoxText, ref bool isChecked,
                                        MessageBoxButtons buttons,
                                        MessageBoxIcon icon,
                                        MessageBoxDefaultButton defaultButton,
                                        MessageBoxOptions options)
        {
            MessageBoxEx messageBox = new MessageBoxEx();

            messageBox.Owner = owner;
            messageBox.Text = text;
            messageBox.Caption = caption;
            messageBox.CheckBoxText = checkBoxText;
            messageBox.CheckBoxChecked = isChecked;
            messageBox.Buttons = buttons;
            messageBox.Icon = icon;
            messageBox.DefaultButton = defaultButton;
            messageBox.Options = options;

            DialogResult res = messageBox.Show();

            isChecked = messageBox.CheckBoxChecked;

            return res;
        }

        /// <summary>
        /// Displays the messagebox.
        /// </summary>
        /// <returns>The <see cref="DialogResult"/> response.</returns>
        public virtual DialogResult Show()
        {
            if (string.IsNullOrEmpty(Text))
                throw (new ApplicationException("Text not set."));

            if (string.IsNullOrEmpty(Caption))
                throw (new ApplicationException("Caption not set."));

            if (string.IsNullOrEmpty(CheckBoxText))
                throw (new ApplicationException("CheckBoxText not set."));

            using (CbtLocalHook hook = new CbtLocalHook())
            {
                hook.WindowCreated += new EventHandler<CbtHookEventArgs>(WindowCreated);
                hook.WindowActivated += new EventHandler<CbtHookEventArgs>(WindowActivated);
                hook.WindowDestroyed += new EventHandler<CbtHookEventArgs>(WindowDestroyed);

                hook.Hook();
                DialogResult result = MessageBox.Show(Owner, Text, Caption, Buttons, 
                                                      Icon, DefaultButton, Options);
                hook.UnHook();

                return result;
            }
        }
        
        void WindowCreated(object sender, CbtHookEventArgs e)
        {
            Debug.WriteLine("WindowCreated: " + e.Title + " [" + e.ClassName + "]" + " " + e.IsDialogWindow);

            if (e.IsDialogWindow)
            {
                _checkBoxCreated = false;
                _windowHandle = e.Handle;
            }
        }

        void WindowActivated(object sender, CbtHookEventArgs e)
        {
            if (e.Handle != _windowHandle)
                return;

            if (_checkBoxCreated) // Create the checkbox only once
                return;
            
            _checkBoxCreated = true;

            // Get the current font, either from the static text window
            // or the message box itself

            using (Font dialogFont = GetDialogFont())
            {
                // Get the x coordinate for the check box.  Align it with the icon if possible,
                // or one character height in

                int x = GetOffsetX();
                
                if(x == -1)
                    x = (int)dialogFont.GetHeight();

                int y = GetOffsetY();

                int height = (int)dialogFont.GetHeight();

                ResizeDialog(height * 2);
                CreateCheckBox(x, y, height, dialogFont);
            }
        }

        private void CreateCheckBox(int x, int y, int height, Font font)
        {
            User32.RECT rc = new User32.RECT();
            User32.GetWindowRect(_windowHandle, rc);

            uint style =  BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP;

            _checkBoxHandle = User32.CreateWindowEx(0, "button", _checkboxText, 
                                                    style,
                                                    x, y, rc.right - rc.left - x, 
                                                    height,
                                                    _windowHandle, IntPtr.Zero, 
                                                    IntPtr.Zero, IntPtr.Zero);

            User32.SendMessage(_checkBoxHandle, WM_SETFONT, (uint)font.ToHfont(), 1);

            if(_checkBoxChecked)
                User32.SendMessage(_checkBoxHandle, BM_SETCHECK, BST_CHECKED, 0);
            else
                User32.SendMessage(_checkBoxHandle, BM_SETCHECK, BST_UNCHECKED, 0);
        }

        private int GetOffsetY()
        {
            User32.RECT rc = new User32.RECT();
            User32.GetClientRect(_windowHandle, rc);

            return rc.bottom - rc.top;
        }

        private void ResizeDialog(int padHeight)
        {
            User32.RECT rc = new User32.RECT();
                        
            User32.GetWindowRect(_windowHandle, rc);
            User32.MoveWindow(_windowHandle, 
                              rc.left, 
                              rc.top, 
                              rc.right - rc.left, 
                              rc.bottom - rc.top + padHeight, 
                              false);
        }

        private int GetOffsetX()
        {
            IntPtr iconHandle = User32.GetDlgItem(_windowHandle, 0x0014);

            if (iconHandle != IntPtr.Zero)
            {
                User32.RECT rcIcon = new User32.RECT();
                User32.GetWindowRect(iconHandle, rcIcon);
                User32.POINT pt = new User32.POINT();
                pt.x = rcIcon.left;
                pt.y = rcIcon.top;
                User32.ScreenToClient(_windowHandle, pt);
                return pt.x;
            }

            return -1;
        }

        private Font GetDialogFont()
        {
            IntPtr fontHandle = IntPtr.Zero;
            IntPtr h = User32.GetDlgItem(_windowHandle, 0xFFFF);

            if (h != IntPtr.Zero)
                fontHandle = (IntPtr)User32.SendMessage(h, WM_GETFONT, 0, 0);
            else
                fontHandle = (IntPtr)User32.SendMessage(h, WM_GETFONT, 0, 0);

            if(fontHandle != IntPtr.Zero)
                return Font.FromHfont(fontHandle);

            return null;
        }

        void WindowDestroyed(object sender, CbtHookEventArgs e)
        {
            if (e.Handle == _windowHandle)
            {
                _checkBoxCreated = false;
                _windowHandle = IntPtr.Zero;

                if (User32.SendMessage(_checkBoxHandle, BM_GETCHECK, 0, 0) == BST_CHECKED)
                    _checkBoxChecked = true;
                else
                    _checkBoxChecked = false;
            }
        }
    }
}
